iT邦幫忙

0

如何用 DOI 找到論文的免費全文 PDF

  • 分享至 

  • xImage
  •  

手上有 DOI,出版社的頁面卻要求登入,而你只是想看一下研究方法那一節。其實很多時候網路上就有合法的免費版本:作者所屬大學的典藏庫、預印本、PubMed Central。手動找的話要試四、五個網站;用程式找的話,只要依合理的順序呼叫幾個公開 API 就行。

下面的順序來自桌面應用程式 osFoundry 的 Paper 工作區裡的 View 按鈕,它的後端會依序嘗試每個來源,直到有一個回傳真正的 PDF 為止(論文寫作與研究工作區頁面有周邊的畫面)。範例使用兩個真實的 DOI,主題相同:手機放在桌上會不會影響專注力。

  • 10.1037/tmb0000123,2024 年的統合分析(Hartanto et al.)
  • 10.1086/691462,被大量引用的 2017 年「Brain Drain」研究(Ward et al.)

在搜尋結果上按 View,就在應用程式內打開統合分析的全文

向 OpenAlex 要所有的位置,不要只看開放取用旗標

OpenAlex 會列出論文被放在哪些地方,以及每個地方有沒有直接的 PDF:

curl -s "https://api.openalex.org/works/doi:10.1037/tmb0000123?select=open_access,best_oa_location,locations"

統合分析這篇會回傳三個位置:出版社的 PDF 連結、James Cook University 典藏庫裡的 PDF,以及 Singapore Management University 圖書館的介紹頁面。

Brain Drain 這篇用同樣的請求,會看到 "is_oa": true 和 "oa_status": "green",但每個 pdf_url 都是 null。唯一的公開版本是典藏庫的介紹頁面。所以 is_oa 只代表某處有免費版本,不代表你下載得到。要看每個位置的 pdf_url。

沒有 API 金鑰的話,OpenAlex 的請求會和同一個 IP 位址的所有人共用一小份免費的每日額度,回應裡的 meta.notice 也會這樣告訴你。申請免費金鑰(在查詢加上 api_key=)就有自己的額度;如果設定了 OPENALEX_API_KEY,應用程式會讀取它。

檢查前五個位元組

pdf_url 也只是一個承諾。出版社常常用 200 狀態碼加上 HTML 內容,回傳 cookie 牆、機器人檢查或登入頁面。應用程式的 Rust 程式碼只在內容以 PDF 的 magic number 開頭時才接受下載:

if !fetched.bytes.starts_with(b"%PDF-") {
    self.tried.push(format!("{source}: link was a web page, not a PDF"));
    return None;
}

同樣的做法用 Python 寫,只需要標準函式庫:

import json
import urllib.error
import urllib.request

UA = {"User-Agent": "paper-finder/1.0"}

def get(url):
    with urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=30) as r:
        return r.read()

def pdf_links(doi):
    try:
        work = json.loads(get(f"https://api.openalex.org/works/doi:{doi}"
                              "?select=best_oa_location,locations"))
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return []
        raise
    links = []
    for loc in [work.get("best_oa_location")] + work.get("locations", []):
        url = (loc or {}).get("pdf_url")
        if url and url not in links:
            links.append(url)
    return links

def first_real_pdf(doi):
    tried = []
    for url in pdf_links(doi)[:4]:
        try:
            body = get(url)
        except (urllib.error.URLError, TimeoutError) as e:
            tried.append(f"{url}: {e}")
            continue
        if body[:5] == b"%PDF-":
            return url, body
        tried.append(f"{url}: a web page, not a PDF")
    raise LookupError("no free PDF; tried: " + "; ".join(tried or ["no pdf_url at all"]))

拿統合分析來跑,出版社的連結拒絕了這支腳本,典藏庫的版本則回傳了一個 1.6 MB 的 PDF。拿 Brain Drain 來跑,會直接以「no pdf_url at all」失敗。把試過哪些連結、為什麼失敗記下來,多寫這幾行很值得;只回一句「沒有全文」,你還是得回頭手動找。

每次查詢最多試四個連結、以及去除重複的 URL,這兩點也是沿用應用程式的做法。有些論文有十幾個位置,其實全都指向同一個檔案。

後面的流程

OpenAlex 的查詢只是其中一步。應用程式完整的順序是:

  1. 搜尋結果本身附帶的 PDF 連結(如果有的話)。
  2. arXiv。形如 10.48550/arXiv.<id> 的 DOI 就是 arXiv 論文,而 https://arxiv.org/pdf/<id> 永遠是免費的。
  3. 這個 DOI 的公開版本,來自 Unpaywall 或 OpenAlex。Unpaywall(https://api.unpaywall.org/v2/<doi>?email=<your email>)會回傳 best_oa_location.url_for_pdf 和 oa_locations 清單,沒有附上真實 Email 地址的請求會被拒絕。這兩個服務來自同一個非營利組織 OurResearch,所以應用程式在設定了 UNPAYWALL_EMAIL 時問 Unpaywall,否則讀 OpenAlex 的位置,不會兩個都問。
  4. CORE。它的 v3 API(/v3/search/works?q=doi:"<doi>",需要 bearer 金鑰)彙整了各典藏庫的版本,常常直接在 fullText 回傳擷取出的文字。文字超過 500 個字元時應用程式就直接採用,否則改試 downloadUrl。
  5. PubMed Central。有 PMCID 的話,Europe PMC 會在 https://www.ebi.ac.uk/europepmc/webservices/rest/<PMCID>/fullTextXML 以 JATS XML 提供整篇文章。它不是 PDF,但章節、圖表和參考文獻都有結構,可以乾淨地轉成 HTML。

摘要是倒排索引

還有一個 OpenAlex 常讓人卡住的細節:沒有 abstract 欄位。你拿到的是 abstract_inverted_index,一個從每個字對應到它出現位置的 map,要自己把文字組回來:

words = {pos: w for w, positions in idx.items() for pos in positions}
abstract = " ".join(words[i] for i in sorted(words))

這樣用在搜尋結果和初步篩選就夠了。像 Brain Drain 這種唯一公開版本只是典藏庫頁面的論文,摘要加上統合分析的全文,大概就是免費管道能走到的最遠處,通常這時候就該查查你的圖書館有沒有訂閱。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言